import hashlib
import hmac
import json
import requests
import time
import urllib
import sys
import datetime

from coin.exchange.base.kr_rest.auth_base import AuthKey
from coin2.api_client.common import (SyncQueryer)

def to_milliseconds(time:datetime.datetime):
  return int(time.timestamp()) * 1000


class BinanceAuth(requests.auth.AuthBase):
  def __init__(self, key):
    self._key = AuthKey.from_file(key)

  def _gen_signature(self, data: bytes):
    signature = hmac.new(self._key.secret_key_bytes, data, digestmod=hashlib.sha256)
    return signature.hexdigest()

  def __call__(self, req: requests.PreparedRequest):
    query = urllib.parse.urlparse(req.url).query
    signature = self._gen_signature(query.encode())
    req.url = '%s&signature=%s' % (req.url, signature)
    req.headers.update({'X-MBX-APIKEY': self._key.access_key})
    return req


class BinancePrivateClient():
  def __init__(self, key_path: str, mea: str, ip=None):
    self._auth = BinanceAuth(key_path)
    self._mea = mea
    if "Futures.Binance.v1-delivery" == mea:
      self._url = "https://dapi.binance.com"
    elif "Futures.Binance.v1" == mea:
      self._url = "https://fapi.binance.com"
    elif "Spot.Binance.v1" == mea:
      self._url = 'https://api.binance.com'
    else:
      assert (0)

    self._query = SyncQueryer(api_host=self._url, auth=self._auth, ip=ip)

  def __check_spot(self):
    assert ("Spot.Binance.v1" == self._mea)

  def place_order(self, symbol: str, side: str, type: str, timeInForce: str, price: str, qty: str):
    method = "POST"
    path = "/fapi/v1/order"
    params = {
        'symbol': symbol,
        'side': side,
        'type': type,
        'timeInForce': timeInForce,
        'price': price,
        'quantity': qty,
        'newClientOrderId': str(int(time.time() * 1000000)),
        'recvWindow': int(time.time() * 1000),
        'timestamp': int(time.time() * 1000),
        'reduceOnly': "false",
    }
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def cancel_order(self, symbol: str, order_id=None, cl_order_id=None):
    method = "DELETE"
    path = "/fapi/v1/order"
    params = {
        'symbol': symbol,
        'recvWindow': 5000,
        'timestamp': int(time.time() * 1000),
    }
    if order_id:
      params['orderId'] = str(order_id)
    if cl_order_id:
      params['origClientOrderId'] = str(cl_order_id)
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def amend_order(self, symbol: str, side: str, price: str, qty: str, order_id=None, cl_order_id=None):
    method = "PUT"
    path = "/fapi/v1/order"
    params = {
        'symbol': symbol,
        'side': side,
        'price': price,
        'quantity': qty,
        'orderId': order_id,
        "origClientOrderId": cl_order_id,
        'recvWindow': int(time.time() * 1000),
        'timestamp': int(time.time() * 1000),
    }
    response = self._query.query(method=method, path=path, params=params)
    print(response.request.url)
    print(response.request.body)
    return response.json()

  def cancel_batch_orders(self, symbol: str, order_ids: list[int]=None, cl_order_ids: list[str]=None):
    method = "DELETE"
    path = "/fapi/v1/batchOrders"
    params = {
        'symbol': symbol,
        'recvWindow': 5000,
        'timestamp': int(time.time() * 1000),
    }
    if order_ids:
      params['orderIdList'] = json.dumps(order_ids, separators=(',', ':'))
    if cl_order_ids:
      params['origClientOrderIdList'] = json.dumps(cl_order_ids, separators=(',', ':'))
    response = self._query.query(method=method, path=path, params=params)
    return response.json()

  def get_open_order(self):
    method = "GET"
    params = {'timestamp': int(time.time() * 1000)}
    if "Futures.Binance.v1-delivery" == self._mea:
      self._path = "/dapi/v1/openOrders"
    elif "Futures.Binance.v1" == self._mea:
      self._path = "/fapi/v1/openOrders"
    else:
      self._path = "/api/v3/openOrders"

    response = self._query.query(method=method, path=self._path, params=params)
    return response.json()

  def get_withdraw_history(self):
    self.__check_spot()
    method = "GET"
    path = "/sapi/v1/capital/withdraw/history"
    params = {'timestamp': int(time.time() * 1000)}
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def get_asset(self):
    method = "GET"
    path = "/sapi/v1/margin/allAssets"
    params = {'asset': "SOL"}
    response = self._query.query(method=method, path=path)
    print(response)
    return response.json()

  def get_position(self):
    method = "GET"
    path = "/fapi/v2/account"
    params = {'timestamp': int(time.time() * 1000)}
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def get_funding_fee(self, start_timestamp: int, end_timestamp: int, limit: int = 1000):
    method = "GET"
    if self._mea == "Futures.Binance.v1-delivery":
      path = "/dapi/v1/income"
    elif self._mea == "Futures.Binance.v1":
      path = "/fapi/v1/income"
    else:
      assert False

    params = {'timestamp': int(time.time() * 1000),
              "incomeType": "FUNDING_FEE",
              "limit": limit,
              "startTime": start_timestamp // 1000000,
              "endTime": end_timestamp // 1000000,
              "recvWindow": 5000}
    response = self._query.query(method=method, path=path, params=params)
    return response.json()

  def get_balance(self):
    method = "GET"
    path = "/api/v3/account"
    params = {'timestamp': int(time.time() * 1000)}
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def get_flexible_product_position(self):
    method = "GET"
    path = "/sapi/v1/simple-earn/flexible/position"
    params = {'timestamp': int(time.time() * 1000)}
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def get_locked_product_position(self):
    method = "GET"
    path = "/sapi/v1/simple-earn/locked/position"
    params = {'timestamp': int(time.time() * 1000)}
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def get_deposit_hisrec(self):
    method = "GET"
    path = "/sapi/v1/capital/deposit/hisrec"
    params = {'timestamp': int(time.time() * 1000)}
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def get_universal_transfer(self):
    mothod = "GET"
    path = "/sapi/v1/sub-account/universalTransfer"
    params = {'timestamp': int(time.time() * 1000),
                               "toEmail": "prestoinvt@gmail.com", "limit": 1, "page": 2}
    response = self._query.query(method=mothod, path=path, params=params)
    return response.json()

  def enable_bind_ip(self, api_key, email, is_restrict=True):
    self.__check_spot()
    method = "POST"
    #path = "/sapi/v1/sub-account/subAccountApi/ipRestriction"
    path = "/sapi/v2/broker/subAccountApi/ipRestriction"
    params = {
        'subAccountApiKey': api_key,
        'email': email,
        'ipRestrict': is_restrict,
        'timestamp': int(time.time() * 1000),
    }
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def add_bind_ip_list(self, api_key, email, ip_list):
    self.__check_spot()
    method = "POST"
    path = "/sapi/v2/sub-account/subAccountApi/ipRestriction"
    params = {
        'subAccountApiKey': api_key,
        'email': email,
        'status': '2',
        'ipAddress': ip_list,
        'timestamp': int(time.time() * 1000),
    }
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def get_bind_ip_list(self, api_key, email):
    self.__check_spot()
    method = "GET"
    path = "/sapi/v1/sub-account/subAccountApi/ipRestriction"
    params = {
        'subAccountApiKey': api_key,
        'email': email,
        'timestamp': int(time.time() * 1000),
    }
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def del_bind_ip_list(self, api_key, email, ip_list):
    self.__check_spot()
    method = "DELETE"
    #path = "/sapi/v1/sub-account/subAccountApi/ipRestriction/ipList"
    path = "/sapi/v2/broker/subAccountApi/ipRestriction/ipList"
    params = {
        'subAccountApiKey': api_key,
        'email': email,
        'ipAddress': ip_list,
        'timestamp': int(time.time() * 1000),
    }
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def get_cross_margin(self):
    self.__check_spot()
    method = "GET"
    path = "/sapi/v1/margin/account"
    params = {
        'timestamp': int(time.time() * 1000),
    }
    response = self._query.query(method=method, path=path, params=params)
    return response.json()

  def get_order_status(self, symbol: str, cl_order_id: str):
    method = "GET"
    path = "/fapi/v1/order"
    params = {
        'symbol': symbol,
        'origClientOrderId': cl_order_id,
        'timestamp': int(time.time() * 1000),
    }
    response = self._query.query(method=method, path=path, params=params)
    print(response)
    return response.json()

  def get_order_fill(self, symbol: str, begin_time, end_time):
    method = "GET"
    path = "/api/v3/myTrades"
    params = {
        'symbol': symbol,
        'startTime': begin_time,
        'endTime': end_time,
        'timestamp': int(time.time() * 1000),
        "limit": 1000
    }
    response = self._query.query(method=method, path=path, params=params)
    return response.json()


  def get_order_fill_range(self, begin:str, end:str, native_symbol:str):
    end = datetime.datetime.strptime(end, "%Y%m%d")
    begin = datetime.datetime.strptime(begin, "%Y%m%d")

    fill_list = []
    while end >= begin:
      end_time = end
      begin_time = end - datetime.timedelta(hours=6)

      info = binance.get_order_fill(native_symbol,  to_milliseconds(begin_time), to_milliseconds(end_time))
      size = len(info)
      if size == 1000:
        raise

      #print(begin_time, end_time, to_milliseconds(begin_time), to_milliseconds(end_time), size)
      fill_list = fill_list + info
      #time.sleep(1)
      end = begin_time

    return fill_list

  def set_leverage(self, symbol, leverage):
    method = 'POST'
    path = '/fapi/v1/leverage'
    params = {
        'symbol': symbol,
        'leverage': leverage,
        'timestamp': int(time.time() * 1000),
    }
    response = self._query.query(method=method, path=path, params=params)
    return response.json()


if __name__ == "__main__":
  key_path = sys.argv[1]
  config = {}
  config["key_path"] = key_path

  binance = BinancePrivateClient(key_path, "Futures.Binance.v1")
  resp = binance.get_open_order()
  #resp = binance.set_leverage("ETHBTC", 5)
  #for r in resp:
  #  if 'EOSUSDT' not in r['symbol']:
  #    continue
  #  print(json.dumps(r, indent=2))
  # place_order(self, symbol: str, side: str, type: str, timeInForce: str, price: str, qty: str)
  #resp = binance.place_order("EOSUSDT", "BUY", "LIMIT", "GTX", "0.70", "10")
  #print(json.dumps(resp, indent=2))
  # cancel_order(self, symbol: str, order_id=None, cl_order_id=None):
  #resp = binance.cancel_order("EOSUSDT", "24494700319")
  #print(json.dumps(resp, indent=2))
  # amend_order(self, symbol: str, side: str, price: str, qty: str, order_id=None, cl_order_id=None)
  #resp = binance.amend_order("EOSUSDT", "BUY", "0.70", "10", "24494700319", "1690510571434039")
  print(json.dumps(resp, indent=2))
  #file_list = binance.get_order_fill_range("20220401", "20220415", "KLAYUSDT")
  #print(file_list)
